/* 
 * DMA
 */
#include "../include/types.h"
#include "../include/const.h"
#include "../include/limits.h"
#include "../include/busIO.h"
#include "../include/mem.h"
#include "../include/kernelheap.h"
#include "../include/paging.h"
#include "../include/monitor.h"
#include "../include/dma.h"

#define DMA_FREE_FRAME  0 /* DMA frame laisvas */
#define DMA_BUSY_FRAME  1 /* DMA frame užimtas */
#define DMA_MEMORY_DIM (DMA_MEMORY_END-DMA_MEMORY_START) /* DMA dydis */

/* DMA kanalai */
static dma_channel_t dmainfo[] ={
  { 0x87, 0x00, 0x01 },
  { 0x83, 0x02, 0x03 },
  { 0x81, 0x04, 0x05 },
  { 0x82, 0x06, 0x07 }
};
extern u32int placement_address;
u8int *dma_free_frames; /* DMA vectorius laisvu frame. i386 pirmus 16MB turi skirti DMA */
/* Perkelimas is hdd į ram ir atvirkščiai, priklauso nuo read, 1- RAM->HDD, 0- hdd->RAM */
void dma_xfer(unsigned channel, u32int physaddr, u32int length, boolean read){
  int page, offset;

  if(channel > 3){
    return;
  }

  /* Apskaičiuojame DMA puslapį ir poslinkį */
  page = physaddr >> 16;
  offset = physaddr & 0xFFFF;
  length -= 1; /* Jei reikia k baitų, k-1 reikia */

  /* disable(); */ /* Pertraukimų išjungimas */

  // set the mask bit for the channel //
  outb(0x0A, channel | 0x04);

  // clear flipflop //
  outb(0x0C, 0x00);

  // set DMA mode (write+single+r/w) //
  outb(0x0B, (read ? 0x48 : 0x44) + channel);

  // set DMA page //
  outb(dmainfo[channel].page, page);

  // set DMA offset //
  outb(dmainfo[channel].offset, offset & 0xFF);  // low byte //
  outb(dmainfo[channel].offset, offset >> 8);    // high byte //

  // set DMA length //
  outb(dmainfo[channel].length, length & 0xFF);  // low byte //
  outb(dmainfo[channel].length, length >> 8);    // high byte //

  // clear DMA mask bit //
  outb(0x0A, channel);

  /* enable(); */ /* Pertraukimų įjungimas */
}

/** @} */ // end of DMADriver

/* DMA inicializacija */
boolean init_dma(){
  u32int i;
  /* DMA laisvu frame'u vectoriaus inicializavimas */
  dma_free_frames =(u8int*) kmalloc(DMA_MEMORY_DIM/4096); /*FFD*/
  memset(dma_free_frames, 0, DMA_MEMORY_DIM/4096);

  /* Nustatyti kernelio užimtus frame */
  for(i=0; i<=placement_address-0x10000; i+=0x1000){
    dma_free_frames[(K_PHYS_ADDR+i)/0x1000] = DMA_BUSY_FRAME;
  }
  return TRUE;
}

/* Gauname pointerį bufferio. Adresas yra ne virtualus! */
void *dma_phys_alloc(u32int len){
  u32int i, j, ret;
  char flag=0;

  /* Ilgis negali but NULL */
  if(!len){
    return(NULL);
  }

  /* Sulygiuojame su puslapiu */
  len = PAGE_ALIGN_UP(len);

  /* Ieškome laisvos DMA vietos */
  for(i=0; i<(DMA_MEMORY_DIM/4096); i++){
    if(dma_free_frames[i] == DMA_FREE_FRAME){ /* Jei radome: */
      /* Dabar dar reikia rasti len/4096-1 kitus laisvus frame */
      for(j=1, flag=0; j<len/4096; j++){
        if(dma_free_frames[i+j] == DMA_BUSY_FRAME){ /* Rastas užimtas */
          flag = 1;
          break;
        }
      }
      if(flag == 1){ /* Nerasta nuosekli reikalaujama vieta, ieškome toliau */
        i += j;
      }
      else{ /* Rasta nuosekli vieta, todėl pažymime ją */
        for(j=0; j<len/4096; j++)
          dma_free_frames[i+j] = DMA_BUSY_FRAME;
        break;
      }
    }
  }
  /* Patikriname ar radome */
  if (flag == 0){ /* Radome */
    ret = (i * 4096 + DMA_MEMORY_START);
  }
  else{ /* Neradome */
    ret =(u32int) NULL;
  }
  return((void *)ret);
}

/* Atlaisviname DMA vietą. Ne virtualiame adrese! */
boolean dma_phys_free(u32int dma_start, u32int len){
  int i;

  if(!len){
    return(FALSE);
  }
  len = PAGE_ALIGN_UP(len); /* Išlygiuojame */
  if((dma_start >= DMA_MEMORY_START) && (dma_start < DMA_MEMORY_END) &&
    ((dma_start + len) < DMA_MEMORY_END)){
    /* sched_enter_critical_region(); */ /* Tam kad multitascingas neveiktų */
    /* Pažymime kad laisva vieta */
    for(i=(dma_start-DMA_MEMORY_START)/4096; i<DMA_MEMORY_DIM/4096; i++){
      dma_free_frames[i] = DMA_FREE_FRAME;
    }
    /* sched_leave_critical_region(); */ /* Tam kad multitascingas neveiktų */
    return(TRUE);
  }
  else{
    return(FALSE); /* Peršoka DMA ribas */
  }
}
